#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

// Specific to emulator
#include <fcntl.h>

// New includes
#include "util.h"
#include "network.h"
#include "queue.h"
#include "forward_table.h"
//#include "topology.h"

/*
 * THIS IS THE EMULATOR
 *
 * emulator -p <port> -q <queue_size> -f <filename> -l <log>
 *
 */

// CONFIGURATION VARIABLES
#define NUM_EMULATOR_ARGS 9
#define HEARTBEAT_INTERVAL 2000 // In milliseconds

int in_port;          // Port on which the sender waits for requests
int queue_size;       // Size of each of the three queues
char *filename;       // File name containing the forwarding table
char *logname;
//FILE *logfile;

int out_port;         // Port on which the requester is waiting
int rate;             // Number of packets to be sent per second
uint32_t seq_no;      // Initial sequence of the packet exhange
uint32_t length;      // Length of the payload in the packets

// DATA STRUCTURES
Queue *prior_1;
Queue *prior_2;
Queue *prior_3;
Forward_Table *table;
Topology_Table *topology;
Em_Packet *heartbeat;
LSP_Packet *lsp_packet;

// HEARTBEAT VARIABLES
long last_heartbeat;
long curr_time;
//int heart_sequence;
char isStartup;

// SOCKET VARIABLES
fd_set readfds;
int sockfd;
int sockflags;
int parse_code;
struct sockaddr_in servaddr, cliaddr;

// TIMEOUT FOR SELECT
#define TIMEOUT_SEC 0
#define TIMEOUT_USEC 0

Em_Packet* init_heartbeat(LSP_Packet *local_packet)
{
  //    heart_sequence = 1;
    Em_Packet *em_heart = (Em_Packet*)heartbeat_packet_buf;
    Packet *heart = (Packet*)em_heart->payload;
    LSP_Packet *lsp_heart = (LSP_Packet*)heart->payload;
    isStartup = 'T';

    // Fill in the LSP_Packet
    int lsp_len = sizeof(LSP_Packet) + sizeof(Node_ID)
                  * local_packet->num_neighbors;
    memcpy(lsp_heart, local_packet, lsp_len);

    // Uncomment for debug
    // print_Node_ID(&(lsp_heart->source));

    // Fill in the Packet
    heart->type = 'L';
    heart->sequence = 0;
    heart->length = lsp_len;

    // Uncomment for debug
    // printf("\n");
    // print_Packet(heart);

    // Fill in the Em_Packet
    em_heart->priority = HIGH; // ADJUST?
    em_heart->src_IP = lsp_heart->source.IP;
    em_heart->src_port = lsp_heart->source.port;
    em_heart->dest_IP = lsp_heart->source.IP;     // Temp filler
    em_heart->dest_port = lsp_heart->source.port; // Temp filler
    em_heart->length = sizeof(Packet) + heart->length;

    // Uncomment for debug
    // print_em_packet(em_heart);

    return em_heart;
}

Em_Packet* update_heartbeat(LSP_Packet *local_packet, char isLocal)
{
    Em_Packet *em_heart = (Em_Packet*)heartbeat_packet_buf;
    Packet *heart = (Packet*)em_heart->payload;
    LSP_Packet *lsp_heart = (LSP_Packet*)heart->payload;

    // Fill in the LSP_Packet
    int lsp_len = sizeof(LSP_Packet) + sizeof(Node_ID)
                  * local_packet->num_neighbors;
    memcpy(lsp_heart, local_packet, lsp_len);

    // Uncomment for debug
    // print_Node_ID(&(lsp_heart->source));

    // Fill in the Packet
    heart->type = 'L';

    if (isLocal == 'T'){
        heart->sequence++;  // 
    }
    else {
        heart->sequence = local_packet->sequence;
    }

    heart->length = lsp_len;

    // Uncomment for debug
    // printf("\n");
    // print_Packet(heart);

    // Fill in the Em_Packet
    em_heart->priority = HIGH; // ADJUST?
    em_heart->src_IP = lsp_heart->source.IP;
    em_heart->src_port = lsp_heart->source.port;
    em_heart->dest_IP = lsp_heart->source.IP;     // Temp filler
    em_heart->dest_port = lsp_heart->source.port; // Temp filler
    em_heart->length = sizeof(Packet) + heart->length;

    // Uncomment for debug
    // print_em_packet(em_heart);

    return em_heart;
}

void UDP_send(char* buf, int buf_len, struct in_addr *IP, int port,
         int sockfd, struct sockaddr_in *pcliaddr, socklen_t clilen){

    // Setup outgoing send
    bzero(pcliaddr, sizeof(*pcliaddr));
    pcliaddr->sin_family = AF_INET;
    pcliaddr->sin_port = htons(port);
    bcopy((char *)IP, (char *)&(pcliaddr->sin_addr.s_addr),
          sizeof(struct in_addr));

    // RELEASE THE KRAKEN
    // (i.e. SEND PACKET)
    int n;
    if ((n = sendto(sockfd, buf, buf_len,
                    0,(struct sockaddr*) pcliaddr, clilen)) == -1)
    {
        perror(ERROR_SENDTO);
        exit(1);
    }
}

// This is based off of the assumption that path in the topology are
// bidirectional as mentioned in the homework specifications
void send_heartbeat(int sockfd, struct sockaddr_in *pcliaddr, socklen_t clilen)
{
    printf("\n");
    print_time();
    printf("\tREADY TO SEND NEW ROUND OF HEARTBEATS\n");

    int i;
    for(i = 0; i < topology->local_packet->num_neighbors; i++)
    {
        if (isStartup == 'F' && topology->local_packet->neighbors[i].isAcked != 'T') {
            printf("\tNo ack received for neighbor (%d): ", i);
	    print_Node_ID(&(topology->local_packet->neighbors[i]));
            printf("\n");

            // Find the ACK'ed neighbor and mark it as ACK'ed
            remove_topo_neighbor(topology,
                &(topology->local_packet->neighbors[i].IP),
                topology->local_packet->neighbors[i].port);
	    i = -1;//i-1;

            heartbeat = update_heartbeat(topology->local_packet, 'T');

	    delete_table(table);
            table = init_table(table, topology);
	    table = build_table(table, topology);



	    continue;
	}

	// Setup information specific to this heartbeat
	heartbeat->dest_IP = topology->local_packet->neighbors[i].IP;
	heartbeat->dest_port = topology->local_packet->
                                         neighbors[i].port;
        //((Packet*)(heartbeat->payload))->sequence = heart_sequence;

	// Send out the heartbeat
        UDP_send((char*)heartbeat,
                 sizeof(Em_Packet) + heartbeat->length,
                 &(topology->local_packet->neighbors[i].IP),
                 topology->local_packet->neighbors[i].port,
		 sockfd, pcliaddr, clilen);
        topology->local_packet->neighbors[i].isAcked = 'F';

	// Uncomment for debug
        // print_em_packet(heartbeat);
        // print_sent_packet((Packet*)heartbeat->payload, pcliaddr, n);
    }

    //heart_sequence++;
    last_heartbeat = get_milli_time();
    print_time();
    printf("\tSent heartbeats (seq_num: %d) to %d neighbors...\n", ((Packet*)(heartbeat->payload))->sequence, i);
}

void createroutes(int sockfd, struct sockaddr_in *pcliaddr, socklen_t clilen)
{
    int n;
    socklen_t len = sizeof(struct sockaddr_in);
    int size = 0; // Used to keep track of total data sent
    int status;
    Em_Packet *recv_packet = NULL;       // Packet received
    struct timeval timeout;

    // Startup send
    send_heartbeat(sockfd, pcliaddr, clilen);
    isStartup = 'F';

    while(1)
    {
        // Flood heartbeats...
        curr_time = get_milli_time();
        if (curr_time - last_heartbeat >= HEARTBEAT_INTERVAL){
            send_heartbeat(sockfd, pcliaddr, clilen);
        }

        // Prepare to select and then select
        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);
	timeout.tv_sec = TIMEOUT_SEC;    // Set timeout
	timeout.tv_usec = TIMEOUT_USEC;  // Set timeout
        status = select(sockfd+1, &readfds, NULL, NULL, &timeout);

	// READY TO RECEIVE DATA!!!
        if(status > 0)
        {
	  printf("HERE 1\n");

            // Receive all types of packets...
            n = recvfrom(sockfd, em_packet_buf, MAX_PACKET_SIZE,
                         0, (struct sockaddr*)pcliaddr, &len);
            em_packet_buf[n] = '\0'; // Null terminate
            if (n == -1)
            {
                perror(ERROR_RECVFROM);
                exit(1);
            }

	  printf("HERE 2\n");

            // Read packet from buffer
            recv_packet = (Em_Packet*)em_packet_buf;
	    packet = (Packet*)recv_packet->payload;
            lsp_packet = (LSP_Packet*)packet->payload;

	    ///////////////////////
	    // CHECK PACKET TYPE //
	    ///////////////////////

	  printf("HERE 3\n");

            // Handle LSP Packet
	    if (packet->type == 'L'){

                // See if the LSP Packet is from a known neighbor
                Node_ID *src_neighbor = get_topo_neighbor(topology,
                                        &(recv_packet->src_IP),
                                        recv_packet->src_port);
		if (src_neighbor == NULL) {

		  printf("GOT AN LSP FOR AN UNKNOWN NEIGHBOR\n");
		  //WORKING HERE!!


		  //LSP_Packet *new_lsp = 
                        add_topo_neighbor(topology, lsp_packet);

                    delete_table(table);
                    table = init_table(table, topology);
                    table = build_table(table, topology);

		    //JUST UNCOMMENTED THIS!
		    // MAJOR ERROR HERE.

		    //Forward the new neighbor
                    //heartbeat =
		    //  update_heartbeat(new_lsp, 'F');

                    //send_heartbeat(sockfd, pcliaddr, clilen);



		    // Send out my own new status
                    heartbeat =
		      update_heartbeat(topology->local_packet, 'T');

                    send_heartbeat(sockfd, pcliaddr, clilen);

		}

		// Check if LSP Packet has a higher sequence number
		else if (get_lsp(topology, 
                         &(lsp_packet->source)) != NULL) {

		  printf("HERE IN NEWLY UNCOMMENTED\n");

                    LSP_Packet *tmp_lsp =
                        update_topo(topology, lsp_packet);

		    if (tmp_lsp == NULL){
		      printf("HAVE NULL TMP_LSP\n");

		    }

		    else{
                    heartbeat =
	                update_heartbeat(tmp_lsp, 'F');
                    send_heartbeat(sockfd, pcliaddr, clilen);
		    }

                }

                // Setup information specific to this heartbeat
	        recv_packet->dest_IP = recv_packet->src_IP;
	        recv_packet->dest_port = recv_packet->src_port;
	        recv_packet->src_IP = topology->local.IP;
	        recv_packet->src_port = topology->local.port;
                ((Packet*)(recv_packet->payload))->type = 'P';

                // Send out the heartbeat ACK
                UDP_send((char*)recv_packet,
                    sizeof(Em_Packet) + recv_packet->length,
                    &(recv_packet->dest_IP),
                    recv_packet->dest_port,
		    sockfd, pcliaddr, clilen);

                continue;
            }

	    // Handle LSP Packet ACK
	    else if (packet->type == 'P'){

		  printf("GOT AN ACK\n");

                // Find the ACK'ed neighbor and mark it as ACK'ed
                Node_ID *src_neighbor = get_topo_neighbor(topology,
                                        &(recv_packet->src_IP),
                                        recv_packet->src_port);
		if (src_neighbor != NULL) {

		  printf("ACKed THIS PACKET: ");
		    print_Node_ID(src_neighbor);

                    src_neighbor->isAcked = 'T';
		    printf(" %c\n", src_neighbor->isAcked);

		}

		else {
		  printf("GOT AN ACK FOR AN UNKNOWN NEIGHBOR\n");
		}
                continue;
	    }

	    // Handle Joey's Packets
	    else if (packet->type == 'T'){


	      printf("ENTERED JOEY's CODE\n");
    		RT_Packet *rt_packet = (RT_Packet*) packet->payload;
		
		Node_ID dest_hop;
		Node_ID next_hop;
		
		//when ttl is 0, we write the current ip to the embedded packet
		//and send it back to the 
		if(rt_packet->ttl == 0){


                    load_rt_packet(rt_packet, 'T', 0, "wut",
                                    rt_packet->src_port,
				   (char*)&(table->local.IP),
                                    (int) table->local.port);
                    load_packet(packet, 'T', 0, sizeof(RT_Packet),
                                    (char*) rt_packet);
                    load_em_packet(recv_packet, 1, "wut",
		    		    recv_packet->src_port, "wut", 0,
                                    sizeof(Packet) +
                                    strlen(packet->payload), packet);

                    UDP_send((char*)recv_packet, 
		    		    sizeof(Em_Packet) + recv_packet->length,
                                    &(rt_packet->src_IP), 
				    next_hop.port,
                                    sockfd, pcliaddr, clilen);


		}

		else{
                    rt_packet->ttl = rt_packet->ttl - 1;
                    load_packet(packet, 'T', 0, sizeof(RT_Packet),
                                    (char*) rt_packet);
                    load_em_packet(recv_packet, 1, "wut", 
                                    recv_packet->src_port, "wut", 0,
                                    sizeof(Packet) +
				    strlen(packet->payload), packet);
		    dest_hop.IP = rt_packet->dest_IP;
		    dest_hop.port = rt_packet->dest_port;	
		    next_hop = *(get_next_hop(table, &dest_hop));

		    UDP_send((char*)recv_packet, sizeof(Em_Packet) +
					recv_packet->length,
					&(rt_packet->dest_IP),
					recv_packet->dest_port,
					sockfd, pcliaddr, clilen);
		}
	    }

	    // If not a special type of packet, do normal forward
	    else {
                print_time();

                print_em_packet(recv_packet);
                print_recv_packet(packet, 
                    (struct sockaddr_in*)pcliaddr, n, size);

		// TODO: NEXT HOP LOOKUP AND FORWARD SEND
	    }
	}

        // SELECT BLEW UP
	else if (status == -1)
        {
            printf("Some error occured on select\n");
        }
    } // End infinite while
}

int main(int argc, char *argv[])
{
    int main_status;

    // Verify number of command line arguments
    if (argc != NUM_EMULATOR_ARGS)
    {
        perror(ERROR_EMULATOR_USAGE);
        exit(1);
    }

    // Parse command line information and set up configurations
    while ((parse_code = getopt(argc, argv, "pqfl:")) != -1)
    {
       switch (parse_code)
       {
           case 'p':
	       in_port = strtol(argv[optind], NULL, 10);
	       if (in_port <= 1024 || in_port >= 65536)
	       {
                   perror(ERROR_PORT_RANGE);
                   exit(1);
	       }
               break;
           case 'q':
               queue_size = strtol(argv[optind], NULL, 10);
               break;
           case 'f':
	     filename = argv[optind];
               break;
           case 'l':
	     logname = optarg;
               break;
           default:
               perror(ERROR_EMULATOR_USAGE);
	       exit(1);
       }
    }
    printf ("\033[34mEMULATOR SETUP WITH THE FOLLOWING CONFIG:\033[1;34m\n> in_port:\t%d\n> queue_size:\t%d\n> filename:\t%s\n> logname:\t%s\033[0m\n", in_port, queue_size, filename, logname);

    // Setup topology table
    topology = init_topo_table(topology, in_port);
    main_status = populate_topo_table(topology, filename);
    if(main_status == -1)
    {
        perror(ERROR_READTOPOLOGY);
        exit(1);
    }
    dump_topo_table(topology);

    table = init_table(table, topology);
    table = build_table(table, topology);

    heartbeat = init_heartbeat(topology->local_packet);
    // Setup queues
    prior_1 = NULL;
    prior_2 = NULL;
    prior_3 = NULL;
    prior_1 = init_queue(prior_1, queue_size);
    prior_2 = init_queue(prior_2, queue_size);
    prior_3 = init_queue(prior_3, queue_size);

    // Create socket
    if ( ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) )
    {
        perror(ERROR_SOCKET);
        exit(1);
    }

    // Make socket non-blocking
    sockflags = fcntl(sockfd, F_GETFL);
    sockflags = sockflags | O_NONBLOCK;
    fcntl(sockfd, F_SETFL, sockflags);    

    // Prepare the in-bound port
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(in_port);

    // Bind the socket
    if (bind(sockfd, (struct sockaddr *) &servaddr,
             sizeof(servaddr)) < 0)
    {
        perror(ERROR_BIND);
        exit(1);
    }

    createroutes(sockfd, &cliaddr, sizeof(cliaddr));

    return 0;
}
